﻿using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Bioinformatics.Types;

namespace Bioinformatics.Algorithms
{
    /// <summary>
    /// Supports the Particle Swarm Optimization Maximum Likelihood functionality with incorporated benchmarking.
    /// </summary>
    public class Psoml
    {
        public Psoml()
        {
            Phylogeny = new Phylogeny();
        }

        /// <summary>
        /// Maximum number of iterations the swarm undergoes.
        /// </summary>
        public int GenerationMax { get; private set; }

        public float GlobalCoefficient { get; private set; }

        public bool IsParallel { get; private set; }


        /// <summary>
        /// Maximum change in branch length per iteration.
        /// </summary>
        public float LengthDeltaMax { get; private set; }

        /// <summary>
        /// Minimum change in branch length per iteration.
        /// </summary>
        public float LengthDeltaMin { get; private set; }

        /// <summary>
        /// Maximum branch length.
        /// </summary>
        public float LengthMax { get; private set; }

        /// <summary>
        /// Minimum branch length.
        /// </summary>
        public float LengthMin { get; private set; }

        public float LocalBestCoefficient { get; private set; }
        public float LocalCoefficient { get; private set; }

        /// <summary>
        /// Size of social group per swarm.
        /// </summary>
        public int NeighborhoodSize { get; private set; }

        public float NeighbourhoodCoefficient { get; private set; }
        public StreamWriter OutputFileStream { get; private set; }
        public Phylogeny Phylogeny { get; private set; }

        /// <summary>
        /// Maximum random component per iteration.
        /// </summary>
        public float RandomVectorMax { get; private set; }

        /// <summary>
        /// Minimum random component per iteration.
        /// </summary>
        public float RandomVectorMin { get; private set; }

        /// <summary>
        /// Gets an array comprising of all particles in the swarm
        /// </summary>
        public Particle[] Swarm { get; private set; }

        public void Run()
        {
            if (IsParallel)
            {
                //initialize swarm
                for (int i = 0; i < Swarm.Length; i++)
                    Swarm[i] = new Particle(Phylogeny, LengthMax, LengthMin, LengthDeltaMax, LengthDeltaMin,
                                            Swarm.Length,
                                            NeighborhoodSize);
                
                var stopWatch = new Stopwatch();
                for (int i = 0; i < GenerationMax; i++)
                {
                    //start stopwatch
                    stopWatch.Start();
                    //calculate likelihood for all particles in swarm
                    Parallel.For(0, Swarm.Length, s => Swarm[s].CalculateLikelihood());
                    //sort the array by likelihood - find global best
                    Array.Sort(Swarm, Particle.Comparer);
                    Particle.BranchLengthGlobalBest = (float[]) Swarm[0].BranchLengths.Clone();
                    //update local best
                    Parallel.For(0, Swarm.Length, s =>
                                                      {
                                                          int nMax = 0;
                                                          for (int n = 0; n < Swarm[s].Neighbourhood.Length; n++)
                                                              if (Swarm[n].Likelihood > Swarm[nMax].Likelihood)
                                                                  nMax = n;
                                                          Swarm[s].BranchLengthsNeighbourhoodBest =
                                                              (float[]) Swarm[nMax].BranchLengths.Clone();
                                                      });
                    //adjust the velocities of each particle
                    Parallel.For(0, Swarm.Length, s => Swarm[s].AdjustVelocities());
                    //update the position of each particle in the swarm
                    Parallel.For(0, Swarm.Length, s => Swarm[s].UpdateBranchLengths());
                    //stop stopwatch
                    stopWatch.Stop();
                    OutputFileStream.WriteLine(i + "," + Swarm[0].Likelihood + "," + stopWatch.ElapsedMilliseconds);
                    stopWatch.Reset();
                }
            }
            else
            {
                //initialize swarm
                for (int i = 0; i < Swarm.Length; i++)
                    Swarm[i] = new Particle(Phylogeny, LengthMax, LengthMin, LengthDeltaMax, LengthDeltaMin,
                                            Swarm.Length,
                                            NeighborhoodSize);
                var stopWatch = new Stopwatch();
                for (int i = 0; i < GenerationMax; i++)
                {
                    //start stopwatch
                    stopWatch.Start();
                    //calculate likelihood for all particles in swarm
                    for (int s = 0; s < Swarm.Length; s++)
                        Swarm[s].CalculateLikelihood();
                    //sort the array by likelihood - find global best
                    Array.Sort(Swarm, Particle.Comparer);
                    Particle.BranchLengthGlobalBest = (float[]) Swarm[0].BranchLengths.Clone();
                    //update local best
                    for (int s = 0; s < Swarm.Length; s++)
                    {
                        int nMax = 0;
                        for (int n = 0; n < Swarm[s].Neighbourhood.Length; n++)
                            if (Swarm[n].Likelihood > Swarm[nMax].Likelihood)
                                nMax = n;
                        Swarm[s].BranchLengthsNeighbourhoodBest = (float[]) Swarm[nMax].BranchLengths.Clone();
                    }
                    //adjust the velocities of each particle
                    for (int s = 0; s < Swarm.Length; s++)
                        Swarm[s].AdjustVelocities();
                    //update the position of each particle in the swarm
                    for (int s = 0; s < Swarm.Length; s++)
                        Swarm[s].UpdateBranchLengths();
                    //stop stopwatch
                    stopWatch.Stop();
                    OutputFileStream.WriteLine(i + "," + Swarm[0].Likelihood + "," + stopWatch.ElapsedMilliseconds);
                    stopWatch.Reset();
                }
            }
        }

        public void SetParametersFromFile(string path)
        {
            //open a file stream to the arguments file
            var streamReader = new StreamReader(path);
            //loop until we've reached the end of the file
            while (!streamReader.EndOfStream)
            {
                //read the current line
                string currentLine = streamReader.ReadLine();
                //get the parameter we're setting
                string parameterName = currentLine.Split('=')[0];
                //perform conditional analysis of the parameter name
                switch (parameterName)
                {
                    case "--processorcount":
                        switch (currentLine.Split('=')[1])
                        {
                            case "1":
                                IsParallel = false;
                                Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(0x0001);
                                break;
                            case "2":
                                IsParallel = true;
                                Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(0x0003);
                                break;
                            case "3":
                                IsParallel = true;
                                Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(0x0007);
                                break;
                            case "4":
                                IsParallel = true;
                                Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(0x000F);
                                break;
                        }
                        break;
                    case "--outputfile":
                        OutputFileStream = new StreamWriter(currentLine.Split('=')[1]);
                        OutputFileStream.WriteLine("Generation, Likelihood, Time per Iteration (ms)");
                        break;
                    case "--maxgenerations":
                        GenerationMax = int.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--currentcoefficient":
                        break;
                    case "--neighborhoodsize":
                        NeighborhoodSize = int.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--localcoefficient":
                        LocalCoefficient = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--globalcoefficient":
                        GlobalCoefficient = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--neighborhoodcoefficient":
                        NeighbourhoodCoefficient = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--localbestcoefficient":
                        LocalBestCoefficient = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--swarmsize":
                        Swarm = new Particle[int.Parse(currentLine.Split('=')[1])];
                        break;
                    case "--lengthdeltamax":
                        LengthDeltaMax = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--lengthdeltamin":
                        LengthDeltaMin = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--lengthmax":
                        LengthMax = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--lengthmin":
                        LengthMin = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--randomvectormax":
                        RandomVectorMax = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--randomvectormin":
                        RandomVectorMin = float.Parse(currentLine.Split('=')[1]);
                        break;
                    case "--topology":
                        Phylogeny.PhylogenyNodes = new DynamicPhylogeny(currentLine.Split('=')[1]).ToStaticPhylogeny();
                        break;
                    case "--sequence":
                        Phylogeny.Sequences.Add(currentLine.Split('=')[1].Split('|')[0],
                                                currentLine.Split('=')[1].Split('|')[1]);
                        break;
                    case "--stationaryprobability":
                        Phylogeny.StationaryProbabilities.Add(currentLine.Split('=')[1].Split('|')[0][0],
                                                              float.Parse(currentLine.Split('=')[1].Split('|')[1]));
                        break;
                }
            }
        }
    }
}